class Store {
    constructor(option) {
        this._option = Object.assign({
            name: "ada-assets",
            version: 1,
            store: 'ada-source-map'
        }, option);
        this._db = null;
        this._dbps = null;
    }

    get option() {
        return this._option;
    }

    getDB() {
        if (!this._db) {
            if (!this._dbps) {
                this._dbps = new Promise((resolve, reject) => {
                    if (!window.indexedDB) {
                        window.indexedDB = window.mozIndexedDB || window.webkitIndexedDB;
                    }
                    let request = window.indexedDB.open(this.option.name, this.option.version);
                    request.onupgradeneeded = (e) => {
                        let db = e.target.result;
                        if (db.objectStoreNames.contains(this.option.store)) {
                            db.deleteObjectStore(this.option.store);
                        }
                        db.createObjectStore(this.option.store);
                    };
                    request.onsuccess = () => {
                        resolve(request.result);
                    };
                    request.onerror = () => reject();
                }).then(db => this._db = db);
            }
            return this._dbps;
        } else {
            return Promise.resolve(this._db);
        }
    }

    get(key) {
        return this.getDB().then(db => {
            return new Promise((resolve, reject) => {
                const transaction = db.transaction([this.option.store], 'readonly');
                const request = transaction.objectStore(this.option.store).get(key);
                transaction.oncomplete = () => resolve(request.result.value);
                transaction.onerror = () => reject();
            });
        });
    }

    set(key, value) {
        return this.getDB().then(db => {
            return new Promise((resolve, reject) => {
                const transaction = db.transaction([this.option.store], 'readwrite');
                transaction.objectStore(this.option.store).put({ key, value }, key);
                transaction.oncomplete = () => resolve();
                transaction.onerror = () => reject();
            });
        });
    }

    getAll() {
        return this.getDB().then(db => {
            return new Promise((resolve, reject) => {
                const transaction = db.transaction([this.option.store], 'readonly');
                const request = transaction.objectStore(this.option.store).getAll();
                transaction.oncomplete = () => {
                    let data = {};
                    request.result.forEach(({ key, value }) => {
                        data[key] = value;
                    });
                    resolve(data);
                };
                transaction.onerror = () => reject();
            });
        });
    }

    setAll(data = {}) {
        return this.getDB().then(db => {
            return new Promise((resolve, reject) => {
                const transaction = db.transaction([this.option.store], 'readwrite');
                const store = transaction.objectStore(this.option.store);
                Reflect.ownKeys(data).forEach(key => {
                    store.put({ key, value: data[key] }, key);
                });
                transaction.oncomplete = () => resolve();
                transaction.onerror = () => reject();
            });
        });
    }

    remove(...args) {
        return this.getDB().then(db => {
            return new Promise((resolve, reject) => {
                const transaction = db.transaction([this.option.store], 'readwrite');
                const store = transaction.objectStore(this.option.store);
                args.forEach(a => {
                    store.delete(a);
                });
                transaction.oncomplete = () => resolve();
                transaction.onerror = () => reject();
            });
        });
    }
}

class CacheStore {
    constructor(option) {
        this._store = new Store(option);
        this._cache = null;
        this._cachePs = null;
    }

    get store() {
        return this._store;
    }

    get cache() {
        return this._cache;
    }

    get(key) {
        return this.getAll().then(data => data[key]);
    }

    set(key, value) {
        return this.getAll().then(data => {
            if (data[key] === undefined) {
                data[key] = value;
                this.store.set(key, value);
            }
        });
    }

    getAll() {
        if (!this._cache) {
            if (!this._cachePs) {
                this._cachePs = this.store.getAll();
            }
            return this._cachePs;
        } else {
            return Promise.resolve(this._cache);
        }
    }

    setAll(data, finder = (key, data, all) => all[key] === undefined) {
        return this.getAll().then(cache => {
            let save = {}, has = false;
            Reflect.ownKeys(data).filter(key => {
                return finder(key, data[key], cache);
            }).forEach(a => {
                save[a] = data[a];
                has = true;
            });
            Object.assign(cache, save);
            if (has) {
                return this.store.setAll(save);
            }
        });
    }
}

module.exports = { Store, CacheStore };